
package SSF.OS;

import com.renesys.raceway.SSF.*;
import com.renesys.raceway.DML.*;

import java.io.*;
import java.util.*;

/** Base class for Protocol implementations.  Specifies default mechanisms
  * for how to behave when configured, when opened by another 
  * ProtocolSession, when a ProtocolMessage is pushed to this protocol by 
  * another ProtocolSession, and so forth.  Subclasses may extend these 
  * behaviors with their own specific behaviors.<BR>
  * Since release 1.4:  Provides uniform support for protocol monitoring
  * by classes implementing interface SSF.OS.ProtocolMonitor.
  * A derived ProtocolSession with monitoring
  * enabled will call monitorReceive, monitorSend, monitorDrop
  * when it receives, sends or drops a message, respectively.
  * A ProtocolMonitor can be added/removed and enabled/disabled at runtime.
  *
  */
public abstract class ProtocolSession implements Configurable {

/** Initialization routine, called by the ProtocolGraph after instantiation.
  * The order of initialization of a set of protocols is unspecified, so 
  * other methods (like open()) may be called before init().  Resources 
  * (like connection tables) that may be needed by open() should 
  * therefore be created in the ProtocolSession constructor.
  */
  public void init() throws ProtocolException {};

/** Routine to call when a message is being sent to ("pushed into") this 
  * ProtocolSession by another ProtocolSession.  The pusher sends a reference
  * to itself in the second argument.  This push happens immediately, without
  * any simulation time elapsing, regardless of other activities taking place 
  * in the ProtocolGraph.  <p>
  *
  * If you desire "safe" interaction with other CPU activities, even though 
  * your push() consumes no measurable/modelable CPU cycles, define the 
  * "cpudelay true" attribute for the ProtocolGraph and use 
  * pushAfterDelay(message,fromSession,0.0).  This will guarantee proper 
  * ordering; that is, the framework will wait until the CPU is free before
  * proceeding with the requested push(). 
  */ 
  public abstract boolean push(ProtocolMessage message, 
			       ProtocolSession fromSession) 
    throws ProtocolException;

/** Push the message into this protocol session after 
 *  the specified number of ticks of CPU time have been 
 *  consumed.  The protocol session contends with other 
 *  system behavior to get the attention of the CPU 
 *  associated with the ProtocolGraph, so the actual 
 *  time between the pushAfterDelay request and the 
 *  actual message push may be longer than requested.
 * 
 *  For example, pushAfterDelay(msg,from,0.) may actually 
 *  cause some time to elapse before the push takes place,
 *  because we will wait for the CPU to become free, even 
 *  though the requested delay is zero.  
 */
    public boolean pushAfterDelay(ProtocolMessage message, 
				  ProtocolSession fromSession,
				  double seconds) 
	throws ProtocolException {
	final ProtocolMessage f_message = message;
	final ProtocolSession f_fromSession = fromSession;

	Resource cpu;
	if (null == (cpu = inGraph().cpu())) 
	    throw new ProtocolException 
		("Cannot pushAfterDelay() without 'cpudelay true'"+
		 " attribute in the protocol graph configuration.");
	
	return cpu.reserve(SSF.Net.Net.seconds(seconds), new Continuation() {
		public void success() {
		    try {
			push(f_message,f_fromSession); 
		    } catch (ProtocolException pex) {
			pushAfterDelayFailed(pex);
		    }
		}
		public void failure(int errno) {
		    pushAfterDelayFailed(new ProtocolException
			("Failure in pushAfterDelay: errno="+errno));
		}
	    }, true); // use blocking reservation
    }

/** Method called back when pushAfterDelay has failed.  The existence 
 *  of this method is unfortunate, but neither the boolean value from
 *  push() nor the ability to throw a ProtocolException are available 
 *  from within the long-distance callback.  This should be a rare 
 *  condition (almost an assertion failure) because of our shared-resource 
 *  delay model.  The failure branch of the continuation used to model
 *  the delay is never called back.  If the underlying push() throws 
 *  a ProtocolException, pushAfterDelayFailed will be called; that's 
 *  presumably a nonrecoverable error anyway resulting from protocol
 *  stack misconfiguration. 
 */
    protected void pushAfterDelayFailed(ProtocolException pex) { 
	System.err.println("** pushAfterDelay() failed: ");
	pex.printStackTrace();
    };
    
//----------------------------------------------------------------------

/** Symbolic name of the protocol this session implements */
  public String name;

/** Protocol class name to instantiate to make this session */
  public String use;

  protected Entity entity;
  
  public void setEntity(Entity entity)
  {
	  this.entity = entity;
  }
  
/** Configure this ProtocolSession. */
  public void config(Configuration cfg) throws configException {
    name = (String)cfg.findSingle("name");
    use  = (String)cfg.findSingle("use");
  }

/** "Type" string identifying the version of this protocol.  
 */
  public String version() { return(name+"::"+use); };

//----------------------------------------------------------------------

  /** Reference to the protocol graph within which this session is 
    * currently configured, or NULL if it stands alone. 
    */
  ProtocolGraph inGraph;

  /** Return a handle to the protocol graph within which this session 
    * is currently configured, or NULL if it stands alone.  A standalone
    * session is a temporary aberration during construction, which will 
    * be corrected when the session opens or is opened by a session 
    * within an existing protocol graph.
    */
  public ProtocolGraph inGraph() {
    return(inGraph);
  };

  /** Set the protocol graph in which this session is configured.  
@exception ProtocolException if the session is already configured in a protocol graph
   */
  public void setGraph(ProtocolGraph G) throws ProtocolException {
    if (inGraph != null) 
      throw new ProtocolException(this+" already in graph "+inGraph);
    else
      inGraph = G;
  }
      
//----------------------------------------------------------------------

  /** Called by a neighboring session to open 
   *  this session.  Default semantics are to immediately respond 
   *  with a confirming callback to opened() on the calling session.
   *  Session subclasses can override this behavior to implement
   *  delayed open (e.g., open-enable). 
@exception ProtocolException if neither the opening session nor the opened session are contained within a valid protocol graph, or if they are already contained within different protocol graphs, or if the opening session could not be added to the list of open sessions, perhaps because the max session count has been exceeded
   */
  public void open(ProtocolSession S, Object request) 
    throws ProtocolException {

      if (inGraph==null || !inGraph().equals(S.inGraph())) {
	if (S.inGraph()==null) {
	  if (inGraph()==null) 
	    throw 
	      new ProtocolException("Sessions not in protocol graph: "+
					 this+" and "+S);
	  else
	    S.inGraph = inGraph();
	}
	else if (inGraph()==null) 
	  inGraph = S.inGraph();
	else 
	  throw new ProtocolException(this+" can't open ProtocolSession "+S+
					   " in different protocol graph.");
      }

      //neighbors.addElement(S);
      S.opened(this);
    }
  
  /** Called by a neighboring session to confirm to this 
   *  session that an open operation has succeeded, and that this 
   *  session is now successfully configured over/under the caller 
   *  session. 
@exception ProtocolException if the opened session could not be added to the list of open sessions, perhaps because the max session count has been exceeded
   */ 
  public void opened(ProtocolSession S) 
    throws ProtocolException {
      //neighbors.addElement(S);
    }

  /** Called by a neighboring session to close (unconfigure)
   *  this session.  Default semantics are to immediately 
   *  respond with a confirming call to closed() on the neighboring
   *  session.  Session subclasses can override this behavior to 
   *  implement delayed close. 
@exception ProtocolException if the closing session could not be removed from the list of open sessions
   */
  public void close(ProtocolSession S) 
    throws ProtocolException {
      //neighbors.removeElement(S);
      S.closed(this);
    }
      
  /** Called by a neighboring session to confirm to this 
   *  session that a close operation has succeeded, and that 
   *  this session is now successfully unconfigured from the 
   *  caller session. 
@exception ProtocolException if the closed session could not be removed from the list of open sessions
   */
  public void closed(ProtocolSession S) 
    throws ProtocolException {
      //neighbors.removeElement(S);
    }

  /** Returns a standardized prefix string "time, NHI address, protocol_name"
   *  for diagnostic messages from this protocol.
   */
  public String debugIdentifier() {
    return(((double)(inGraph().now()))/((double)SSF.Net.Net.frequency)
           + " host " + ((SSF.Net.Host)inGraph()).nhi
           + " in " + name + ": ");
  }


}


/*=                                                                      =*/
/*=  Copyright (c) 1997--2001  SSF Research Network                      =*/
/*=                                                                      =*/
/*=  SSFNet is open source software, distributed under the GNU General   =*/
/*=  Public License.  See the file COPYING in the 'doc' subdirectory of  =*/
/*=  the SSFNet distribution, or http://www.fsf.org/copyleft/gpl.html    =*/
/*=                                                                      =*/
